#!/usr/bin/env python
# -*- coding: utf-8 -*-
import ctypes
import os
import time
import traceback

import _ctypes

if os.name in ("nt", "ce"):
    dlopen = _ctypes.LoadLibrary
    dlclose = _ctypes.FreeLibrary
elif os.name == "posix":
    dlopen = _ctypes.dlopen
    dlclose = _ctypes.dlclose


class YaPySerialError(Exception):
    """Exception class"""

    def __init__(self, msg):
        """
        Args:
            msg:
        """
        self.msg = msg

    def __str__(self):
        return "Exception in YaPySerial : " + str(self.msg)


class YaPySerial:
    def __init__(self, LibFile):
        """
        Args:
            LibFile:
        """
        self.port = None
        self._DlibraryHandle = None
        self.DlibraryHandle = None
        try:
            """
            Load library
            """
            self._DlibraryHandle = dlopen(LibFile)
            self.DlibraryHandle = ctypes.CDLL(LibFile, handle=self._DlibraryHandle)
            """
            Attach functions
            """
            self._SerialOpen = self.DlibraryHandle.yapy_serial_open
            self._SerialOpen.restype = ctypes.c_int
            self._SerialOpen.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p, ctypes.c_int,
                                         ctypes.c_char_p, ctypes.c_int]

            self._SerialClose = self.DlibraryHandle.yapy_serial_close
            self._SerialClose.restype = ctypes.c_int
            self._SerialClose.argtypes = [ctypes.POINTER(ctypes.c_void_p)]

            self._SerialRead = self.DlibraryHandle.yapy_serial_read
            self._SerialRead.restype = ctypes.c_int
            self._SerialRead.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.c_size_t]

            self._SerialWrite = self.DlibraryHandle.yapy_serial_write
            self._SerialWrite.restype = ctypes.c_int
            self._SerialWrite.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_void_p, ctypes.c_size_t]

            self._SerialGPIO = self.DlibraryHandle.yapy_serial_gpio
            self._SerialGPIO.restype = ctypes.c_int
            self._SerialGPIO.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_int, ctypes.c_int]
        except:
            raise YaPySerialError("Could'n t load dynamic library!")

    def Open(self, device, baud, modestr, timeout):
        """
        Args:
            device:
            baud:
            modestr:
            timeout:
        """
        self.port = ctypes.c_void_p(0)
        try:
            res = int(self._SerialOpen(ctypes.byref(self.port), ctypes.c_char_p(device.encode()), ctypes.c_int(baud),
                                       ctypes.c_char_p(modestr.encode()), ctypes.c_int(timeout)))
        except Exception as ex:
            print(str(ex))
            print(traceback.format_exc())
            raise YaPySerialError("Runrtime error on serial open!")
        if res > 0:
            msg = "Couldn't open serial port, error: " + str(res) + "!"
            raise YaPySerialError(msg)

    def Close(self):
        if self.port:
            res = int(self._SerialClose(ctypes.byref(self.port)))
            if res > 0:
                msg = "Couldn't close serial port, error: " + str(res) + "!"
                raise YaPySerialError(msg)
        self.port = None

    def Read(self, nbytes):
        """
        Args:
            nbytes:
        """
        try:
            buf = ctypes.create_string_buffer(int(nbytes))
            res = int(
                self._SerialRead(ctypes.byref(self.port), ctypes.cast(buf, ctypes.c_void_p), ctypes.c_size_t(nbytes)))
        except:
            raise YaPySerialError("Runrtime error on serial read!")
        if res > 0:
            if res == 2:
                return None
            else:
                msg = "Couldn't read serial port, error: " + str(res) + "!"
                raise YaPySerialError(msg)
        else:
            return buf.raw

    def Write(self, buf):
        """
        Args:
            buf:
        """
        try:
            nbytes = len(buf)
            strbuf = ctypes.create_string_buffer(buf)
            res = int(self._SerialWrite(ctypes.byref(self.port), ctypes.cast(ctypes.byref(strbuf), ctypes.c_void_p),
                                        ctypes.c_size_t(nbytes)))
        except:
            raise YaPySerialError("Runrtime error on serial write!")
        if res > 0:
            msg = "Couldn't write to serial port, error: " + str(res) + "!"
            raise YaPySerialError(msg)
        return nbytes

    def Flush(self):
        try:
            buf = ctypes.create_string_buffer(1);
            res = 0
            t1 = time.time()
            while res == 0 and time.time() - t1 < 1:
                res = int(
                    self._SerialRead(ctypes.byref(self.port), ctypes.cast(buf, ctypes.c_void_p), ctypes.c_size_t(1)))
        except:
            raise YaPySerialError("Runrtime error on serial flush!")
        # if res != 2:
        #     msg = "Couldn't write to serial port, error: " + str(res) + "!"
        #     raise YaPySerialError(msg)

    def GPIO(self, n, level):
        """
        Args:
            n:
            level:
        """
        try:
            res = int(self._SerialGPIO(ctypes.byref(self.port), ctypes.c_int(n), ctypes.c_int(level)))
        except:
            raise YaPySerialError("Runrtime error on serial gpio!")
        if res > 0:
            msg = "Couldn't write to serial port, error: " + str(res) + "!"
            raise YaPySerialError(msg)

    def __del__(self):
        if self._DlibraryHandle is not None:
            if self.port is not None:
                try:
                    self.Close()
                except:
                    raise YaPySerialError("Could'n t close serial port!")
            dlclose(self._DlibraryHandle)
            self._DlibraryHandle = None
            self.DlibraryHandle = None


if __name__ == "__main__":

    """
    "C:\Program Files\Beremiz\python\python.exe" YaPySerial.py
    """
    if os.name in ("nt", "ce"):
        lib_ext = ".dll"
    else:
        lib_ext = ".so"

    TestLib = os.path.dirname(os.path.realpath(__file__)) + "/../../../YaPySerial/bin/libYaPySerial" + lib_ext
    if (os.name == 'posix' and not os.path.isfile(TestLib)):
        TestLib = "libYaPySerial" + lib_ext

    TestSerial = YaPySerial(TestLib)
    TestSerial.Open("COM256", 9600, "8N1", 2)

    TestSerial.Flush()

    send_str = "Hello World!!!"
    TestSerial.Write(send_str)

    receive_str = TestSerial.Read(len(send_str))

    print("Received:")
    print(receive_str)

    TestSerial.Close()

    time.sleep(1)
